Why
專案寫到這裡只是加上註冊登入,我們的 route.php 已經快到進入萬言書的狀態,日後維護跟新增功能恐怕很麻煩,所以我們要想辦法優化。那怎麼優化呢?一樣先找出共通點,很輕易的可以發現他們都是 switch { case } 的架構。所以我覺得這些 case: 可以抽成一頁頁的 php 檔案,通通放在一個資料夾來管理;另外也發現處理表單的動作常常放在產生畫面的資料邏輯上方。這樣每個檔案由三個區塊構成:表單邏輯、頁面所需的資料邏輯、頁面組合。這在一開始 index.php 的時候,真的很可愛,很好看懂。但是當三個區塊都長大到很恐怖的時候,就只能保佑自己一年後看得懂,保佑你的同事打開檔案的時候沒有立刻走出去抽煙喝咖啡。所以我們可以再把這一坨拆成兩塊:表單邏輯自成一頁、頁面資料與頁面組合放一起。
How
怎麼做呢?我先創一個資料夾 controller 專門放這些頁面,把 route.php 的 case 通通拆出來用 parameter 來命名。
如果是表單邏輯我用 do 開頭來命名
如果是頁面邏輯就用 URL 變數 來命名
route.php 改成用參數決定載入 controller 資料夾下存在的檔案名
<?php
$parameter = strtolower($route->getParameter(1));
$controller_array = scandir('controller');
$controller_array = array_change_key_case($controller_array, CASE_LOWER);
if (in_array($parameter.'.php', $controller_array)) {
include( 'controller/'.$parameter.'.php' );
}else{
include( 'controller/login.php' ); // 預設讀取登入頁
}
拆完之後發現一個問題,表單邏輯層如果檢查出資料驗證問題,有錯誤訊息要回傳,該怎麼回傳呢?用 header 導向也不好帶著一堆文字陣列資料回來。於是我發現各家框架都有使用 flash session 它的概念是,這資料只存到下一頁載入後,我覺得根本就是為了解決傳遞錯誤訊息這件事而生的。找到套件直接下指令吧。
composer require plasticbrain/php-flash-messages
官方說明書
簡單介紹用法如下,他自帶 bootstrap 的 class 所以如果我們有套 bootstrap 就會自動變漂亮
<?php
$msg = new \Plasticbrain\FlashMessages\FlashMessages();
// Add messages
$msg->info('This is an info message');
$msg->success('This is a success message');
$msg->warning('This is a warning message');
$msg->error('This is an error message 1');
$msg->error('This is an error message 2'); // 可以塞多個
// If you need to check for errors (eg: when validating a form) you can:
if ($msg->hasErrors()) {
$msg->display(); // 最後一次全部顯示出來
} else {
// There are NOT any errors
}
效果展示
如果我們在 init.php 先全域宣告了這個物件
$msg = new \Plasticbrain\FlashMessages\FlashMessages();
這樣一來我們之前檢查 $error 那段都可以拔掉改成這樣:
<?php if ($msg->hasMessages()) $msg->display(); ?>
view/body/login.php 全文如下
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3">
<form role="form" method="post" action="<?=Config::BASE_URL?>do_login" autocomplete="off">
<h2>Please Login</h2>
<p><a href='register'>Register a New Account</a></p>
<hr>
<?php if ($msg->hasMessages()) $msg->display(); ?>
<div class="form-group">
<input type="text" name="username" id="username" class="form-control input-lg" placeholder="User Name" value="<?php if($msg->hasErrors()){ echo htmlspecialchars($_POST['username'], ENT_QUOTES); } ?>" tabindex="1">
</div>
<div class="form-group">
<input type="password" name="password" id="password" class="form-control input-lg" placeholder="Password" tabindex="3">
</div>
<div class="row">
<div class="col-xs-9 col-sm-9 col-md-9">
<a href='forget'>Forgot your Password?</a>
</div>
</div>
<hr>
<div class="row">
<div class="col-xs-6 col-md-6"><input type="submit" name="submit" value="Login" class="btn btn-primary btn-block btn-lg" tabindex="5"></div>
</div>
</form>
</div>
</div>
</div>
controller/login.php 只剩少少的頁面組合,因為把表單邏輯抽成 do_login.php
<?php
/**
* 載入頁面
*/
//define page title
$title = 'Login';
include('view/header/default.php'); // 載入共用的頁首
include('view/body/login.php'); // 載入登入用的頁面
include('view/footer/default.php'); // 載入共用的頁尾
在 do_login.php 裡面我把錯誤用迴圈通通送進 $msg 再送回上一頁即可回到登入頁告知錯誤原因
foreach( $error as $e) {
$msg->error($e);
}
header('Location: ' . $_SERVER['HTTP_REFERER']);
exit;
do_login.php 全部的代碼是這樣
<?php
if(isset($_POST['submit']))
{
$error = array();
$gump = new GUMP();
$_POST = $gump->sanitize($_POST);
$validation_rules_array = array(
'username' => 'required|alpha_numeric|max_len,20|min_len,3',
'password' => 'required|max_len,20|min_len,3'
);
$gump->validation_rules($validation_rules_array);
$filter_rules_array = array(
'username' => 'trim|sanitize_string',
'password' => 'trim',
);
$gump->filter_rules($filter_rules_array);
$validated_data = $gump->run($_POST);
if($validated_data === false) {
$error = $gump->get_readable_errors(false);
} else {
// basic validation successful
foreach($validation_rules_array as $key => $val) {
${$key} = $_POST[$key]; // trans to local parameters
}
$userVeridator = new UserVeridator();
$userVeridator->loginVerification($username, $password);
$error = $userVeridator->getErrorArray();
if(count($error) == 0){
$table = "members";
$condition = "username = :username";
$order_by = "1";
$fields = "*";
$limit = "LIMIT 1";
$data_array = array(":username" => $username);
$result = Database::get()->query($table, $condition, $order_by, $fields, $limit, $data_array);
$_SESSION['memberID'] = $result[0]['memberID'];
$_SESSION['username'] = $username;
header('Location: home');
exit;
}
}
if(isset($error) AND count($error) > 0){
foreach( $error as $e) {
$msg->error($e);
}
header('Location: ' . $_SERVER['HTTP_REFERER']);
exit;
}
}else{
header('Location: ' . Config::BASE_URL);
exit;
}
效果如圖
我也改寫了其他頁面邏輯,包括註冊、開通、驗證重置密碼、忘記密碼等等
因為太多了,就不一一展示,放上 github 給讀者直接下載來參考